// 导入 mysql 包
const mysql = require("mysql");
const { readFile } = require("./utils");

// 将回调函数封装为 Promise 方式
class MySql {
  static engine = "mysql";
  // 构造函数
  constructor(config) {
    // 产生数据库连接池对象： pool
    this.conn = mysql.createPool(config);
  }
  /**
   * query
   * 执行 SQL 语句并返回结果
   *
   * @param {string} sql 语句
   * @param {Array<any>} values 参数数组
   * @returns {Promise<Array>}
   */
  async #query(sql, ...values) {
    // 兼容使用ES6的标签模板
    if (Array.isArray(sql)) {
      sql = String.raw({ raw: sql }, ...new Array(values.length).fill("?"));
    } else [values] = values;
    // 封装 mysql.pool.query 成 Promise 对象
    return new Promise((resolve, reject) => {
      this.conn.query(sql, values, (error, results, fields) => {
        if (error) return reject(error);
        resolve([results, fields]);
      });
    });
  }
  /**
   * select
   * 适合执行 SELECT SQL 语句，返回查询结果。
   *
   * @param {string} sql select 语句
   * @param {array} values 参数数组
   * @returns {Promise<Array<any>>} 查询结果数组
   */
  async select(sql, ...values) {
    const [results] = await this.#query(sql, ...values);
    return results;
  }
  /**
   * insert 执行 Insert SQL 语句，返回插入语句的 id。
   * @param {string} sql insert 语句
   * @param {Array<any>} values 参数数组
   * @returns {Promise<number>} 插入语句的 id
   */
  async insert(sql, ...values) {
    const [results] = await this.#query(sql, ...values);
    return results.insertId;
  }
  /**
   * findOne
   * 只返回一条记录，适合执行聚合的语句。
   *
   * @param {string} sql select 语句
   * @param {Array} values 参数数组
   * @returns {Promise<any>} 返回一条记录
   */
  async findOne(sql, ...values) {
    const [[one]] = await this.#query(sql, ...values);
    return one;
  }
  /**
   * run 执行不需
   */
  async run(sql, ...values) {
    await this.#query(sql, ...values);
  }
  /**
   * close
   * 关闭连接池对象
   *
   * @returns {void}
   */
  async close() {
    return new Promise((resolve, reject) => {
      this.conn.end((err) => {
        if (err) return reject(err);
        resolve();
      });
    });
  }
}

const sqlite3 = require("sqlite3-offline-next").verbose();

class Sqlite {
  static engine = "sqlite";
  /**
   * @param filename {string} Database filename.
   */
  constructor(filename) {
    this.conn = new sqlite3.Database(filename);
  }
  /**
   * parse
   * @param sql {string}
   * @param values {any[]}
   *
   * @returns {[string, any[] | undefined]}
   */
  #parse(sql, ...values) {
    if (Array.isArray(sql)) {
      sql = String.raw({ raw: sql }, ...new Array(values.length).fill("?"));
    } else [values] = values;
    return [sql.trim(), values];
  }
  // 查询
  async select(sql, ...values) {
    [sql, values] = this.#parse(sql, ...values);
    return new Promise((resolve, reject) => {
      this.conn.all(sql, values, (err, rows) => {
        if (err) return reject(err);
        resolve(rows);
      });
    });
  }
  // 单独查询
  async findOne(sql, ...values) {
    [sql, values] = this.#parse(sql, ...values);
    return new Promise((resolve, reject) => {
      this.conn.get(sql, values, (err, row) => {
        if (err) return reject(err);
        resolve(row);
      });
    });
  }
  // 插入
  async insert(sql, ...values) {
    [sql, values] = this.#parse(sql, ...values);
    return new Promise((resolve, reject) => {
      this.conn.run(sql, values, (err) => {
        if (err) return reject(err);
        this.conn.get("select last_insert_rowid() as id", (err, row) => {
          if (err) return reject(err);
          resolve(row.id);
        });
      });
    });
  }
  /**
   * @param sql {string} sql 
   */
  // 修改
  async run(sql, ...values) {
    [sql, values] = this.#parse(sql, ...values);
    return new Promise((resolve, reject) => {
      this.conn.run(sql, values, (err) => {
        if (err) return reject(err);
        resolve();
      });
    });
  }

  async close() {
    return new Promise((resolve, reject) => {
      this.conn.close((err) => {
        if (err) return reject(err);
        resolve();
      });
    });
  }
}

function createDB() {
  const { DATABASE_URL = 'mysql://root:admin123@localhost/oademo'} = process.env;
  const [scheme, uri] = DATABASE_URL.split("://");
  if (["sqlite3", "sqlite"].includes(scheme)) {
    const db = new Sqlite(uri);
    // execute init sql script
    readFile("./sql/schema-sqlite.sql", "utf-8").then((script) => {
      script.split(";").forEach((sql) => {
        sql = sql.trim();
        if (!sql) return;
        db.run(sql).catch((err) => console.log(err));
      });
    });
    return db;
  } else if (scheme == "mysql") return new MySql(DATABASE_URL);
  console.error("Unknown DATABASE_URL:", DATABASE_URL);
  process.exit();
}

const db = createDB();

module.exports = { db };
